package com.chenju.invokechainservice.service.impl;

import com.chenju.invokechainservice.adapter.MethodNodeAdapter;
import com.chenju.invokechainservice.dao.TMethodNodeRepository;
import com.chenju.invokechainservice.domain.*;
import com.chenju.invokechainservice.service.*;
import com.chenju.invokechainservice.utils.FunnyListsUtils;
import com.chenju.invokechainservice.utils.FunnyStringUtils;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodInsnNode;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Service
public class TMethodNodeServiceImplement implements TMethodNodeService {
    private final TMethodNodeRepository repository;
    private final TAnnotationNodeService annotationNodeService;
    private final TInterfaceNodeService interfaceNodeService;
    private final TParameterNodeService parameterNodeService;

    public TMethodNodeServiceImplement(TMethodNodeRepository repository, TAnnotationNodeService annotationNodeService, TInterfaceNodeService interfaceNodeService, TParameterNodeService parameterNodeService) {
        this.repository = repository;
        this.annotationNodeService = annotationNodeService;
        this.interfaceNodeService = interfaceNodeService;
        this.parameterNodeService = parameterNodeService;
    }

    @Override
    public TMethodNode save(TMethodNode node) {
        return repository.save(node);
    }

    @Override
    public TMethodNode findByName(String name) {
        return repository.findTMethodNodeByName(name);
    }

    @Override
    public TMethodNode findByNameAndDesc(String name, String desc) {
        return repository.findTMethodNodeByNameAndDescriptor(name, desc);
    }

    @Override
    public TMethodNode parseMethod(TMethodNode node, MethodNodeAdapter mna) {
        if (node == null) {
            return null;
        }
        if (mna.desc == null || "".equals(mna.desc)) {
            return node;
        }

        String descriptor = mna.desc;

        // 设置node的descriptor
        node.setDescriptor(descriptor);

        // 解析方法返回类型
        String returnType = Type.getReturnType(descriptor).getClassName();
        node.setReturnType(returnType);

        // 解析参数类型并创建参数节点
        List<Type> argumentTypes = FunnyListsUtils.asArrayList(Type.getArgumentTypes(descriptor));
        List<TParameterNode> parameters = new ArrayList<>();
        if (!CollectionUtils.isEmpty(argumentTypes)) {
            for (Type argumentType : argumentTypes){
                String argumentTypeClassName = argumentType.getClassName();
                parameters.add(parameterNodeService.save(new TParameterNode(argumentTypeClassName, node.getName())));
            }
        }
        node.setParameters(parameters);

        // 解析exception
        if (!CollectionUtils.isEmpty(mna.exceptions)) {
            node.setThrownExceptions(mna.exceptions);
        }

        // 解析注解
        if (!CollectionUtils.isEmpty(mna.visibleAnnotations)) {
            node = (TMethodNode) annotationNodeService.parseAnnotation(node, mna.visibleAnnotations);
        }

        return node;
    }

    @Override
    public TMethodNode parseMethodInvoke(TMethodNode node, MethodNodeAdapter mna) {
        if (node == null) {
            return null;
        }
        if (mna == null) {
            return node;
        }
        List<MethodInsnNode> invokeMethods = mna.invokeMethods;
        if (!CollectionUtils.isEmpty(invokeMethods)) {
            List<TMethodNode> invokeMethodNodes = new ArrayList<>();
            for (MethodInsnNode methodInsnNode : invokeMethods) {
                String invokeMethodName = FunnyStringUtils.findDotString(methodInsnNode.owner + "." + methodInsnNode.name);
                TMethodNode invokeMethodNode = findByNameAndDesc(invokeMethodName, methodInsnNode.desc);
                if (invokeMethodNode == null) {
                    invokeMethodNode = new TMethodNode(invokeMethodName, methodInsnNode.desc);
                    save(invokeMethodNode);
                }
                invokeMethodNodes.add(invokeMethodNode);
            }
            node.setInvokeMethods(invokeMethodNodes);
        }
        return node;
    }


}
